﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using Collab.Common;
using Collab.Services.Remote.Entities;
using EnvDTE80;

namespace Collab.Modules.Session
{
    public class SolutionTracker : ObservableObject, INotifyPropertyChanged, IDisposable
    {
        protected readonly EnvDTE80.DTE2 dte;
        protected string solutionPath;
        protected EnvDTE.SolutionEvents solutionEvents;
        protected EnvDTE.ProjectItemsEvents projectItemsEvents;
        protected IList<string> projectItemsToIgnore = new List<string>();
        protected Solution solution;
        public string SolutionPath
        {
            get
            {
                if (string.IsNullOrEmpty(this.solutionPath))
                {
                    EnvDTE80.Solution2 temp = this.dte.Solution as EnvDTE80.Solution2;
                    ParseSolutionName(temp);
                }
                return this.solutionPath;
            }
        }

        #region RX Events

        private ISubject<ProjectItem> whenItemAddedSubject = new Subject<ProjectItem>();
        private ISubject<ProjectItem> whenItemRemovedSubject = new Subject<ProjectItem>();
        private ISubject<Solution> whenSolutionOpenedSubject = new ReplaySubject<Solution>(1);
        private ISubject<Unit> whenSolutionClosedSubject = new Subject<Unit>();

        public IObservable<ProjectItem> WhenFileAdded
        {
            get { return this.whenItemAddedSubject.AsObservable(); }
        }

        public IObservable<ProjectItem> WhenFileRemoved
        {
            get { return this.whenItemRemovedSubject.AsObservable(); }
        }

        public IObservable<Unit> WhenClosedSolution
        {
            get { return this.whenSolutionClosedSubject.AsObservable(); }
        }

        public IObservable<Solution> WhenOpenedSolution
        {
            get { return this.whenSolutionOpenedSubject.AsObservable(); }
        }

        #endregion

        private bool hasOpenSolution;
        public bool HasOpenSolution
        {
            get
            {
                return this.hasOpenSolution;
            }
            set
            {
                if (this.hasOpenSolution == value)
                    return;
                this.hasOpenSolution = value;
                this.NotifyPropertyChanged("HasOpenSolution");
            }
        }

        public SolutionTracker(EnvDTE80.DTE2 dte)
        {
            Debug.Assert(dte != null);
            this.dte = dte;
            this.solutionEvents = dte.Events.SolutionEvents;
            this.projectItemsEvents = (dte.Events as Events2).ProjectItemsEvents;

            this.HasOpenSolution = this.dte.Solution != null;

            this.projectItemsEvents.ItemAdded += OnFileAdded;
            this.projectItemsEvents.ItemRemoved += OnFileRemoved;
            this.solutionEvents.Opened += OnOpenSolution;
            this.solutionEvents.AfterClosing += OnAfterClosing;
        }

        void OnOpenSolution()
        {
            whenSolutionOpenedSubject.OnNext(ParseSolution());
            this.HasOpenSolution = this.dte.Solution != null;
            Debug.WriteLine("------------------>Opening solution");
        }

        void OnAfterClosing()
        {
            whenSolutionClosedSubject.OnNext(new Unit());
            this.HasOpenSolution = this.dte.Solution != null;
            Debug.WriteLine("------------------>Closing solution");
        }

        private void OnFileAdded(EnvDTE.ProjectItem item)
        {
            
            ProjectItem tempItem = new ProjectItem();
            tempItem.Kind = item.Kind;
            tempItem.Name = item.Name;
            ParseParents(tempItem, item.Collection.Parent);
            tempItem.RelativePath = GetRelativePath(item.FileNames[1], this.solutionPath);
            InsertProjectItemToSolution(tempItem);

            if (this.projectItemsToIgnore.Contains(item.FileNames[1]))
                return;

            this.whenItemAddedSubject.OnNext(tempItem);
        }

        private static void ParseParents(ProjectItem tempItem, object parent)
        {

            List<string> parents = new List<string>();
            while (parent != null)
            {
                if (parent is EnvDTE.Project)
                {
                    var project = parent as EnvDTE.Project;
                    parents.Add(project.Name);
                    parent = project.ParentProjectItem;
                }
                else
                {
                    var temp = parent as EnvDTE.ProjectItem;
                    parents.Add(temp.Name);
                    parent = temp.Collection.Parent;
                }
            }

            tempItem.Parents = parents.Reverse<string>().ToArray();
        }


        /// <summary>
        /// Inserts the ProjectItem in the Solution according to it's parent path
        /// </summary>
        /// <param name="projectItem"></param>
        protected void InsertProjectItemToSolution(ProjectItem projectItem)
        {
            PerformActionToParentsChildrenCollection(projectItem, (item, children) => children.Add(item));
        }

        /// <summary>
        /// Removes the ProjectItem already in the Solution structure that matches the Name and Parent of the argument
        /// </summary>
        /// <param name="projectItem"></param>
        protected void RemoveProjectItemFromSolution(ProjectItem projectItem)
        {
            PerformActionToParentsChildrenCollection(projectItem, (item, children) => children.RemoveAll(child => child.Name == item.Name));   
        }

        /// <summary>
        /// Finds the parent of the ProjectItem in the Solution and then performs the action on the childrencollection of the parent 
        /// </summary>
        /// <param name="projectItem"></param>
        /// <param name="action">The first argument is the ProjectItem and the second parameter is the Children collection of the parent </param>
        protected void PerformActionToParentsChildrenCollection(ProjectItem projectItem, Action<ProjectItem, List<ProjectItem>> action){
            if (projectItem.Parents.Length == 0)
            {
                action(projectItem, this.solution.Children);
            }
            else
            {
                Action<ProjectItem, string[], int> recurseDown = null;
                recurseDown = (tempItem, parentsArray, index) =>
                {
                    if (index < (parentsArray.Length - 1))
                        recurseDown(tempItem.Children.First(item => item.Name == parentsArray[index]), parentsArray, index++);
                    else
                        action(projectItem, tempItem.Children);
                };
                recurseDown(this.solution.Children.First(item => item.Name == projectItem.Parents[0]), projectItem.Parents, 1);
            }
        }



        private void OnFileRemoved(EnvDTE.ProjectItem item)
        {
            ProjectItem tempItem = new ProjectItem();
            tempItem.Kind = item.Kind;
            tempItem.Name = item.Name;
            ParseParents(tempItem, item.Collection.Parent);
            RemoveProjectItemFromSolution(tempItem);

            this.whenItemRemovedSubject.OnNext(tempItem);
        }

        protected string ParseSolutionName(EnvDTE80.Solution2 solution)
        {
            if (this.dte.Solution == null)
                return null;

            if (string.IsNullOrEmpty(solution.FullName))
                return null;

            var arr = solution.FullName.Split(System.IO.Path.DirectorySeparatorChar);
            this.solutionPath = string.Join(System.IO.Path.DirectorySeparatorChar.ToString(), arr.Take(arr.Length - 1));
            return arr[arr.Length - 1];
        }

        public Solution ParseSolution()
        {
            if (!this.HasOpenSolution)
                return null;
            if (this.dte.Solution == null)
                return null;

            this.solution = new Solution();
            EnvDTE80.Solution2 temp = this.dte.Solution as EnvDTE80.Solution2;
            this.solution.Name = ParseSolutionName(temp);

            this.solution.SolutionPath = this.solutionPath;

            this.solution.Children = temp.Projects.Cast<EnvDTE.Project>()
                .Where(item => item.ProjectItems != null)
                .Select(item =>
                    {
                        ProjectItem tempItem = new ProjectItem();
                        tempItem.Kind = item.Kind;
                        tempItem.Name = item.Name;
                        tempItem.FullPath = item.FullName;
                        tempItem.RelativePath = GetRelativePath(item.FullName, this.solutionPath);
                        tempItem.Children = item.ProjectItems
                        .Cast<EnvDTE.ProjectItem>()
                        .Select(RecurseProjectItem)
                        .ToList();
                        return tempItem;
                    })
                    .ToList();

            return this.solution;
        }


        protected object FindProjectItemParentByParents(string[] parents)
        {
            object parent = null;
            for (int i = 0; i < parents.Length; i++)
            {
                if (i == 0)
                {
                    foreach (EnvDTE.Project item in this.dte.Solution.Projects)
                    {
                        if (item.Name == parents[i])
                        {
                            parent = item;
                        }
                    }
                }
                else
                {
                    if (parent is EnvDTE.Project)
                    {
                        var project = parent as EnvDTE.Project;
                        foreach (EnvDTE.ProjectItem subItem in project.ProjectItems)
                        {
                            if (subItem.Name == parents[i])
                            {
                                parent = subItem;
                            }
                        }
                    }
                    else
                    {
                        var temp = parent as EnvDTE.ProjectItem;
                        foreach (EnvDTE.ProjectItem subItem in temp.ProjectItems)
                        {
                            if (subItem.Name == parents[i])
                            {
                                parent = subItem;
                            }
                        }
                    }
                }
            }
            return parent;
        }

        private ProjectItem RecurseProjectItem(EnvDTE.ProjectItem item)
        {
            ProjectItem projectItem = new Services.Remote.Entities.ProjectItem();
            projectItem.Name = item.Name;
            projectItem.Kind = item.Kind;
            Debug.WriteLine("Itemkind " + item.Kind + " Itemname:" + item.Name);
            projectItem.RelativePath = GetRelativePath(item.FileNames[1] ?? GetName(item), this.solutionPath);
            projectItem.FullPath = item.FileNames[1] ?? GetName(item);

            if (item.Kind == EnvDTEConstants.vsProjectItemKindSolutionItems || item.Kind == EnvDTEConstants.vsProjectKindSolutionItems)
            {
                if (item.SubProject != null)
                {
                    projectItem.Children = item.SubProject.ProjectItems
                        .Cast<EnvDTE.ProjectItem>()
                        .Where(subItem => subItem.Name != item.Name)
                        .Select(RecurseProjectItem)
                        .ToList();
                }
            }
            else if (item.ProjectItems != null)
            {
                Debug.WriteLine("Parsing children in" + projectItem.Name);
                projectItem.Children = item.ProjectItems
                    .Cast<EnvDTE.ProjectItem>()
                    .Select(RecurseProjectItem)
                    .ToList();
            }
            return projectItem;
        }

        private string GetName(EnvDTE.ProjectItem item)
        {
            if (item.Object is EnvDTE.Project)
            {
                return (item.Object as EnvDTE.Project).FullName;
            }
            throw new NotSupportedException("The item was not supported");
        }

        private string GetRelativePath(string fullPath, string solutionPath)
        {
            return fullPath.Replace(solutionPath + System.IO.Path.DirectorySeparatorChar, "");
        }




        #region IDisposable Members

        public void Dispose()
        {

            this.dte.Events.MiscFilesEvents.ItemAdded -= OnFileAdded;
            this.dte.Events.MiscFilesEvents.ItemRemoved -= OnFileRemoved;
            this.dte.Events.SolutionEvents.Opened -= OnOpenSolution;
            this.dte.Events.SolutionEvents.AfterClosing -= OnAfterClosing;
        }

        #endregion
    }
}
